﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RentAWorker.Business.DTO;
using RentAWorker.Business.Interfaces;
using RentAWorker.Business.Impl.Util;
using Dal = RentAWorker.DataAccess.Interfaces;
using Entities = RentAWorker.DataAccess.Entities;
using RentAWorker.DataAccess.Interfaces;
using System.Transactions;
using RentAWorker.DataAccess.Entities;
using System.ServiceModel.Activation;
using System.Security.Permissions;
using System.ServiceModel;
using System.Diagnostics;
using System.Security.Principal;
using System.Security;

namespace RentAWorker.Business.Impl
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class RentRequestFacade : IRentRequestService
    {
        private Dal.ICustomerAccess CustomerDal;
        private Dal.IRentalServiceAccess RentalServiceDal;
        private Dal.IResourceAccess ResourceDal;
        private Dal.IMachineTypeAccess MachineTypeDal;
        private Dal.ILocationAccess LocationDal;

        public RentRequestFacade()
        {
            CustomerDal = DataAccessFactory.GetCustomerAccess();
            LocationDal = DataAccessFactory.GetLocationAccess();
            ResourceDal = DataAccessFactory.GetResourceAccess();
            RentalServiceDal = DataAccessFactory.GetRentalServiceAccess();
            MachineTypeDal = DataAccessFactory.GetMachineTypeAccess();
        }

        #region IRentRequestService Members

        [PrincipalPermission(SecurityAction.Demand, Role = "FrontOffice")]
        public RentAWorker.Business.DTO.SearchResourceResponse SearchAvailableResources(RentAWorker.Business.DTO.SearchResourceRequest request)
        {
            //
            // Start with the actual work
            //
            int? MachineTypeId = null;
            Entities.Location Location = null;

            // Pre-Process the request with useful parameters
            if (string.IsNullOrEmpty(request.Name)) request.Name = string.Empty;
            if (string.IsNullOrEmpty(request.State)) request.State = string.Empty;
            if (request.Location >= 0)
            {
                var Locations = LocationDal.GetLocations(string.Empty);
                if (Locations.Count() > 0)
                    Location = Locations.SingleOrDefault(ll => ll.LocationID == request.Location);
            }
            else
            {
                throw new ArgumentException("Location ID cannot be negative!");
            }
            if (Location == null) throw new ArgumentException();
            if (request.MachineType >= 0)
            {
                var MachineTypes = MachineTypeDal.GetMachineType(string.Empty);
                if (MachineTypes != null)
                {
                    var mt = MachineTypes.SingleOrDefault(mm => mm.MachineTypeID == request.MachineType);
                    if (mt == null)
                        MachineTypeId = null;
                    else
                        MachineTypeId = mt.MachineTypeID;
                }
            }

            // Now perform the actual work
            List<Worker> Persons = new List<Worker>();
            List<Machine> Machines = new List<Machine>();

            IEnumerable<Entities.RentResource> Resources;
            Resources = ResourceDal.GetAvailableResources(request.Name, request.FromDate,
                                                          request.ToDate, Location.LocationID,
                                                          MachineTypeId);

            foreach (Entities.RentResource r in Resources)
            {
                if (r is Entities.Machine)
                {
                    Machines.Add((Machine)r);
                }
                else
                {
                    Persons.Add((Worker)r);
                }
            }

            // Finally create the response
            SearchResourceResponse Response = new SearchResourceResponse();
            Response.PersonResource = Persons.ToArray();
            Response.MachineResources = Machines.ToArray();
            return Response;
        }

        //[PrincipalPermission(SecurityAction.Demand, Role = "FrontOffice")]
        public RentAWorker.Business.DTO.RentalServiceResponse RequestRentalService(RentAWorker.Business.DTO.RentalServiceRequest request)
        {
            //
            // Perform some security checks
            // 
            if ((System.Threading.Thread.CurrentPrincipal == null) || 
                (!System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated))
            {
                throw new SecurityException("No user currently logged into the system!");
            }

            if (!System.Threading.Thread.CurrentPrincipal.IsInRole("FrontOffice"))
            {
                throw new SecurityException("Only front-office applications may request rental services!");
            }

            if (OperationContext.Current != null)
            {
                IIdentity PrimaryId = OperationContext.Current.ServiceSecurityContext.PrimaryIdentity;
                if (PrimaryId.Name.Equals("webapp"))
                {
                    EventLog.WriteEntry("Application",
                                        string.Format("Incoming request by web application for customer {0}", request.CustomerId),
                                        EventLogEntryType.Information);
                }
                else
                {
                    EventLog.WriteEntry("Application",
                                        string.Format("Incoming request by user for customer {0}, {1}!", request.CustomerId, PrimaryId.Name),
                                        EventLogEntryType.Information);
                }
            }
            else
            {
                EventLog.WriteEntry("Application",
                     string.Format("Business Component request hosted in web application, directly (no WCF) for customer {0}", request.CustomerId),
                     EventLogEntryType.Information);
            }

            //
            // Start with the actual work
            //
            Entities.RentalService NewService = null;
            RentalServiceResponse Response = new RentalServiceResponse();

            // Create a new Transaction
            TransactionOptions Options = new TransactionOptions();
            Options.IsolationLevel = IsolationLevel.Serializable;

            using (TransactionScope Scope = new TransactionScope(TransactionScopeOption.Required, Options))
            {
                // Verify incoming parameters
                if (request == null) throw new ArgumentNullException("Request cannot be null!");
                if (request.CustomerId < 0) throw new ArgumentException("Customer ID cannot be negative!");
                if (request.ResourceId < 0) throw new ArgumentException("Resource ID cannot be negative!");
                if (request.FromDate <= DateTime.Now) throw new ArgumentException("Start-date cannot begin in the past!");
                if (request.ToDate == DateTime.MaxValue) throw new ArgumentException("End-date cannot be infinite!");
                if (request.FromDate > request.ToDate) throw new ArgumentException("Begin-date cannot be after end-date!");

                // Try to find referenced objects
                Entities.Customer Customer = CustomerDal.GetCustomerById(request.CustomerId);
                if (Customer == null) throw new ArgumentException(string.Format("Customer with ID {0} does not exist!", request.CustomerId));
                Entities.RentResource Resource = ResourceDal.GetResource(request.ResourceId);
                if (Resource == null) throw new ArgumentException(string.Format("Resource with ID {0} does not exist!", request.ResourceId));
                Entities.RentalServiceState State = RentalServiceDal.GetRentalServiceStates().First();
                if (State == null) throw new ArgumentException(string.Format("Requested state does not exist!"));
                Entities.Location Location = null;
                if (request.LocationId >= 0)
                {
                    var Locations = LocationDal.GetLocations(string.Empty);
                    if (Locations.Count() > 0)
                        Location = Locations.SingleOrDefault(ll => ll.LocationID == request.LocationId);
                }
                else
                {
                    throw new ArgumentException("Location ID cannot be negative!");
                }
                if (Location == null) throw new ArgumentException(string.Format("Requested location does not exist!"));

                // Verify if resource is still available
                var ExistingRentalServices =
                    RentalServiceDal.GetRentalServicesByResource(
                        Resource.ResourceID, request.FromDate, request.ToDate);

                if (ExistingRentalServices.Count() > 0)
                {
                    Response.Information = "Resource not available at requested time!";
                    Response.Succeeded = false;
                }
                else if (Resource.Location.LocationID != request.LocationId)
                {
                    Response.Information = "Resource not available for requested location!";
                    Response.Succeeded = false;
                }
                else
                {
                    // Get the RentalService instance from the DAL
                    NewService = new RentAWorker.DataAccess.Entities.RentalService();
                    NewService.Customer = Customer;
                    NewService.Resource = Resource;
                    NewService.StartDate = request.FromDate;
                    NewService.EndDate = request.ToDate;
                    NewService.RentalServiceState = State;
                    NewService = RentalServiceDal.CreateRentalService(NewService);

                    // Complete the transaction
                    Scope.Complete();

                    // Add the new service to the response
                    Response.Succeeded = true;
                    Response.Information = "New request added!";
                    Response.Services = new RentalService[] { NewService };
                }
            }

            // Return the final response
            return Response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "FrontOffice")]
        public RentAWorker.Business.DTO.RentalServiceResponse GetRentalServiceForCustomer(RentAWorker.Business.DTO.RentalServiceForCustomerRequest request)
        {
            // Verify incoming parameters 
            if (request == null) throw new ArgumentNullException("Request cannot be null!");
            if (request.CustomerId < 0) throw new ArgumentException("Customer ID cannot be negative!");
            if (request.RentalServiceId < 0) throw new ArgumentException("Rental Service ID cannot be negative!");

            // Try fetching the dependent objects
            Entities.Customer Customer = CustomerDal.GetCustomerById(request.CustomerId);
            if (Customer == null) throw new ArgumentException(string.Format("Customer with the ID {0} does not exist!", request.CustomerId));

            // Now try fetching the rental services
            Entities.RentalService RentalService = RentalServiceDal.GetRentalService(request.RentalServiceId);
            if (RentalService == null) throw new ArgumentException(string.Format("Rental service with the ID {0} does not exist!", request.RentalServiceId));

            List<RentalService> Services = new List<RentalService>();
            Services.Add(RentalService);

            // Create the response and return to the client
            RentalServiceResponse Response = new RentalServiceResponse();
            Response.Services = Services.ToArray();
            return Response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "FrontOffice")]
        public RentAWorker.Business.DTO.RentalServiceResponse GetRentalServicesForCustomer(RentAWorker.Business.DTO.RentalServicesForCustomerRequest request)
        {
            // Verify incoming parameters 
            if (request == null) throw new ArgumentNullException("Request cannot be null!");
            if (request.CustomerId < 0) throw new ArgumentException("Customer ID cannot be negative!");
            if (request.FromDate.Date < DateTime.Now.Date) throw new ArgumentException("Start-date cannot begin in the past!");
            if (request.ToDate == DateTime.MaxValue) throw new ArgumentException("End-date cannot be infinite!");
            if (request.FromDate > request.ToDate) throw new ArgumentException("Begin-date cannot be after end-date!");

            // Try fetching the dependent objects
            Entities.Customer Customer = CustomerDal.GetCustomerById(request.CustomerId);
            if (Customer == null) throw new ArgumentException(string.Format("Customer with the ID {0} does not exist!", request.CustomerId));

            // Now try fetching the rental services
            List<RentalService> Services = new List<RentalService>();
            foreach (var Service in RentalServiceDal.GetRentalServicesByCustomer(request.CustomerId))
            {
                Services.Add(Service);
            }

            // Create the response and return to the client
            RentalServiceResponse Response = new RentalServiceResponse();
            Response.Services = Services.ToArray();
            return Response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "FrontOffice")]
        public RentAWorker.Business.DTO.RentalServiceResponse ChangeRentalServiceForCustomer(RentAWorker.Business.DTO.RentalServiceChangeRequest request)
        {
            // Verify the incoming parameters
            if (request.RentalServiceId < 0) throw new ArgumentException("The Rental Service ID cannot be negative!");
            if (request.FromDate <= DateTime.Now) throw new ArgumentException("Start-date cannot begin in the past!");
            if (request.ToDate == DateTime.MaxValue) throw new ArgumentException("End-date cannot be infinite!");
            if (request.FromDate > request.ToDate) throw new ArgumentException("Begin-date cannot be after end-date!");

            // Get the rental service request
            Entities.RentalService Service = RentalServiceDal.GetRentalService(request.RentalServiceId);
            if (Service == null) throw new ArgumentException(string.Format("Rental Service with ID {0} does not exist!", request.RentalServiceId));

            // Now delete or update the rental service
            if (request.Cancel)
            {
                RentalServiceDal.DeleteRentalService(Service.RentalServiceID);
            }
            else
            {
                Service.StartDate = request.FromDate;
                Service.EndDate = request.ToDate;
                RentalServiceDal.UpdateRentalService(Service);
            }

            // Create the response
            RentalServiceResponse Response = new RentalServiceResponse();
            Response.Services = new RentalService[] { Service };
            return Response;
        }

        #endregion

        #region Private Helper Methods

        private void ResolveEntitiesByIds(RentalService service, out RentAWorker.DataAccess.Entities.Customer c, out Entities.RentResource r, out Entities.RentalServiceState s)
        {
            // Try to find referenced objects
            c = CustomerDal.GetCustomerById(service.Customer.CustomerID);
            if (c == null) throw new ArgumentException(string.Format("Customer with ID {0} does not exist!", service.Customer.CustomerID));
            r = ResourceDal.GetResource(service.Resource.ResourceID);
            if (r == null) throw new ArgumentException(string.Format("Resource with ID {0} does not exist!", service.Resource.ResourceID));
            s = RentalServiceDal.GetRentalServiceStates().SingleOrDefault(st => (string.Compare(st.RentalServiceStateName, service.RentalServiceState.RentalServiceStateName, true) == 0));
            if (s == null) throw new ArgumentException(string.Format("Requested state does not exist!"));
        }

        #endregion
    }
}
